﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ShortcutFinderHelper.cs" company="TechBits">
//   Copyright (c) TechBits. All rights reserved.
// </copyright>
// <author>
//   Leandro G. Vacirca.
// </author>
// <summary>
//   Defines the ShortcutsFinderHelper type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace IntelliShortCut.Application.Helpers
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    
    using Data;
    using IWshRuntimeLibrary;
    using Microsoft.Win32;
     
    /// <summary>
    /// Provides helper methods for finding shortcuts.
    /// </summary>
    public class ShortcutFinderHelper
    {
        #region "Constants"

        /// <summary>
        /// Registry key for listing installed applications.
        /// </summary>
        private const string REGISTRY_KEY = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

        #endregion

        #region "Private Members"

        /// <summary>
        /// Unique instance of the ShortcutsFinderHelper class.
        /// </summary>
        private static readonly ShortcutFinderHelper _instance = new ShortcutFinderHelper();

        #endregion

        #region "Constructors"

        /// <summary>
        /// Initializes static members of the <see cref="ShortcutFinderHelper"/> class.
        /// </summary>
        /// <remarks>
        /// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit.
        /// </remarks>
        static ShortcutFinderHelper()
        {
            return;
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="ShortcutFinderHelper"/> class from being created.
        /// </summary>
        private ShortcutFinderHelper()
        {
        }

        #endregion

        #region "Public Properties"

        /// <summary>
        /// Gets the unique instance of the ShortcutsFinderHelper class.
        /// </summary>
        public static ShortcutFinderHelper Instance
        {
            get { return _instance; }
        }

        #endregion

        #region "Public Methods"

        /// <summary>
        /// Gets the candidate shortcuts based on the analysis of installed applications on Windows.
        /// </summary>
        /// <returns>
        /// A list of candidate shortcuts based on the analysis of installed applications on Windows.
        /// </returns>
        public List<ShortcutData> GetCandidateShortcuts()
        {
            try
            {
                var links = FilterLinksToApplications(GetProgramFilesApplications(".lnk"));
                var mscs = GetProgramFilesApplications(".msc");

                return GetInstalledApplications().Concat(links).Distinct().Concat(mscs)
                    .Select(f =>
                            new ShortcutData
                                {
                                    Active = false,
                                    ByNameOrAlias = true,
                                    Category = ShortcutCategoryEnum.Files,
                                    Name = Path.GetFileNameWithoutExtension(f),
                                    Alias = string.Empty,
                                    Icon = ShortcutApl.ExtractShortcutIcon(f),
                                    FullPath = f
                                })
                    .ToList();
            }
            catch (Exception)
            {
                return null;
            }
        }

        #endregion

        #region "Private Methods"

        /// <summary>
        /// Gets the installed applications on Windows.
        /// </summary>
        /// <returns>
        /// A list with the full path of the installed application.
        /// </returns>
        private static List<string> GetInstalledApplications()
        {            
            var appFiles = new List<string>();

            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(REGISTRY_KEY))
            {
                if (key != null)
                {
                    var query = from a in key.GetSubKeyNames()
                                let r = key.OpenSubKey(a)
                                select new
                                           {
                                               Application = r.GetValue("DisplayName"),
                                               InstallLocation = r.GetValueNames().Contains("InstallLocation") ? (string)r.GetValue("InstallLocation") : null
                                           };

                    // Gets the applications which have the install location defined
                    var apps = query.Where(a => a.Application != null && !String.IsNullOrEmpty(a.InstallLocation));

                    // Gets executable application files
                    foreach (var app in apps)
                    {
                        var appPath = Path.GetDirectoryName(app.InstallLocation);
                        var appDirInfo = new DirectoryInfo(appPath);
                        appFiles.AddRange(appDirInfo.GetFiles("*.exe", SearchOption.TopDirectoryOnly).Select(f => f.FullName).ToList());
                    }
                }
            }

            return appFiles;
        }

        /// <summary>
        /// Search files.
        /// </summary>
        /// <param name="startDirectory">The start directory.</param>
        /// <param name="extension">The extension.</param>
        /// <returns>
        /// A list of full path files.
        /// </returns>
        private static List<string> SearchFiles(string startDirectory, string extension)
        {
            var files = GetFilesRecursive(startDirectory);
            return files.Where(f => f.EndsWith(extension)).Select(f => f).ToList();
        }

        /// <summary>
        /// Recurses through directories to find file names.
        /// </summary>
        /// <param name="startDirectory">The start directory.</param>
        /// <returns>
        /// All files on all directories starting from the start directory.
        /// </returns>
        private static List<string> GetFilesRecursive(string startDirectory)
        {
            // Store results in the file results list
            var result = new List<string>();

            // Store a stack of our directories
            var stack = new Stack<string>();

            // Add initial directory
            stack.Push(startDirectory);

            // Continue while there are directories to process
            while (stack.Count > 0)
            {
                // Get top directory
                string dir = stack.Pop();

                try
                {
                    // Add all files at this directory to the result List
                    result.AddRange(Directory.GetFiles(dir, "*.*"));

                    // Add all directories at this directory
                    foreach (string dn in Directory.GetDirectories(dir))
                    {
                        stack.Push(dn);
                    }
                }
                catch (Exception)
                {
                }
            }

            return result;
        }

        /// <summary>
        /// Gets theaplication files on the ProgramFiles directory.
        /// </summary>
        /// <param name="extension">The extension.</param>
        /// <returns>
        /// A list with the full path of the executable files on ProgramFiles directory.
        /// </returns>
        private static List<string> GetProgramFilesApplications(string extension)
        {
            var linkFiles = new List<string>();
            var specialFolderPaths = new List<DirectoryInfo>
                                         {
                                             // new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
                                             // new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)),
                                             // new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles)),
                                             // new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)),
                                             // new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)),
                                             new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Programs)),
                                             new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu)),
                                             new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Startup)),
                                             new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.System)),
                                         };

            var programFilesX86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
            if (!string.IsNullOrEmpty(programFilesX86))
            {
                specialFolderPaths.Add(new DirectoryInfo(programFilesX86));
            }

            var programData = Environment.GetEnvironmentVariable("ProgramData");
            if (!string.IsNullOrEmpty(programData))
            {
                specialFolderPaths.Add(new DirectoryInfo(programData));
            }

            foreach (var specialFolderPath in specialFolderPaths)
            {
                try
                {
                    var files = SearchFiles(specialFolderPath.FullName, extension);
                    linkFiles.AddRange(files);
                }
                catch (Exception)
                {
                    continue;
                }
            }

            return linkFiles;
        }

        /// <summary>
        /// Filters lnk files that points to executable applications.
        /// </summary>
        /// <param name="source">The source (list of full path lnk files).</param>
        /// <returns>
        /// Filtered lnk files that points to executable applications.
        /// </returns>
        private static List<string> FilterLinksToApplications(IEnumerable<string> source)
        {
            var links = new List<string>();
            foreach (var lnkFile in source)
            {
                var shell = new WshShell();
                var link = (IWshShortcut)shell.CreateShortcut(lnkFile);

                if (link.TargetPath.EndsWith(".exe") && link.TargetPath.IndexOf(".exe") == link.TargetPath.LastIndexOf(".exe") && !link.TargetPath.ToLower().Contains("unins"))
                {
                    links.Add(link.TargetPath);
                }
            }

            return links.Distinct().ToList();
        }

        #endregion
    }
}